#include <irrlicht.h>
#include <iostream>
#include <fstream>
#include <windows.h>
#include "scnedit.h"
#include "scn/scn.h"
#include "util.h"
#include "scnmesh.h"
#include "EventReceiver.h"
#include "main.h"
#include "selector.h"
#include "CSEditGUI.h"
#include "export.h"

using namespace irr;

using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;

using namespace std;

void buildGui(IrrlichtDevice * device);

IrrlichtDevice * device =0;
CScn * scn = 0;
IMeshSceneNode * node=0;
SMesh * scnMesh = 0;
scene::ITriangleSelector* selector = 0;
ICameraSceneNode * camera = 0;
bool selected=false;
SMaterial redmat;

//SMeshBuffer * buffer=0;

E_MATERIAL_TYPE prevmt;
int selsurf=-1;
array<u32> allselsurfs;

int selsolid=0;

f32 uvgridmult = 0.01;

ofstream * output=0;

char * BaseDirectory;

bool uvresize = false;

u16 GLOBALALPHA = 0;


//selections
CSelector * sel = 0;

CSEditGUI * segui;
int main(int argc, char** argv)
{
    u32 resx=800, resy=600, nbits = 16;
    bool bFullScreen = false;
    video::E_DRIVER_TYPE    driv=EDT_OPENGL;
    char * startmap = 0;
    bool bInvertMouse = false;

    //WASD mapping for camera
    SKeyMap keyMap[4];
    keyMap[0].Action = EKA_MOVE_FORWARD;
    keyMap[0].KeyCode = KEY_KEY_W;
    keyMap[1].Action = EKA_MOVE_BACKWARD;
    keyMap[1].KeyCode = KEY_KEY_S;
    keyMap[2].Action = EKA_STRAFE_LEFT;
    keyMap[2].KeyCode = KEY_KEY_A;
    keyMap[3].Action = EKA_STRAFE_RIGHT;
    keyMap[3].KeyCode = KEY_KEY_D;



//  read command line options
    for (u32 i=1; i<argc; i++)
    {
        if (str_equals(argv[i],"-directx") ||
            str_equals(argv[i],"-dx"))
        {
            driv = EDT_DIRECT3D9;
        }
        else if (str_equals(argv[i],"-res"))
        {
            resx = atoi(argv[++i]);
            resy = atoi(argv[++i]);
        }
        else if (str_equals(argv[i],"-fullscreen") ||
                 str_equals(argv[i],"-f"))
        {
            bFullScreen = true;
        }
        else if (str_equals(argv[i],"-32bits"))
        {
            nbits = 32;
        }
        else if (str_equals(argv[i],"-map"))
        {
            startmap = argv[++i];
        }
        else if (str_equals(argv[i],"-forcealpha") ||
                 str_equals(argv[i],"-fa"))
        {
            GLOBALALPHA = 125;
        }
        else if (str_equals(argv[i],"-invertmouse") ||
                 str_equals(argv[i],"-im"))
        {
            bInvertMouse = true;
        }
        else
        {
            SAY("Unknown option %s. \nAvailable options are:\n\n", argv[i]);
            SAY("   -res <width> <height>   Set screen resolution.\n"
                "   -f, -fullscreen         Run in full screen mode.\n"
                "   -32bits                 Set pixel resolution to 32bits. Default is 16.\n"
                "                           In window mode this is ignored.\n"
                "   -map <mapname>          Load <mapname> on start.\n"
                "   -dx, -directx           Force Direct3d9 mode. Default is OpenGL.\n"
                "   -fa, -forcealpha        Forces showing transparent materials.\n"
                "   -im, -invertmouse       Inverts mouse. Because, you know, we are people too!"
                "\n");

            exit(1337);
        }
    }

    BaseDirectory = new char[256];
    int ret=GetCurrentDirectory(256,BaseDirectory);
    if (!ret || ret>256)
        error(true,"GetCurrentDirectory failed. %d",ret);

    device =
        createDevice(driv, dimension2d<u32>(resx, resy), nbits,
            bFullScreen, false, false, 0);

    //read data.res
    device->getGUIEnvironment()->getFileSystem()
        ->addFileArchive("sedata.res",true,true,EFAT_ZIP);

    //device->setResizable(true);
    device->setWindowCaption(L"SWAT3 SCN editor");
    MyEventReceiver receiver(device);

    device->setEventReceiver(&receiver);

    IVideoDriver* driver = device->getVideoDriver();
    //driver->setTextureCreationFlag(ETCF_ALWAYS_32_BIT);
    ISceneManager* smgr = device->getSceneManager();
    //IGUIEnvironment* guienv = device->getGUIEnvironment();

    segui = new CSEditGUI(device, uvresize, uvgridmult);
    //buildGui(device);

//  camera = device->getSceneManager()->addCameraSceneNodeMaya();
    camera = device -> getSceneManager()->addCameraSceneNodeFPS(0,100,0.5,-1,keyMap,4,false,0.f,bInvertMouse);
    camera->setPosition(core::vector3df(0,0,0));
    camera->setInputReceiverEnabled(false);

    if (startmap)
    {
        path scnfname = path(startmap);
        if (!hasFileExtension(scnfname,"scn"))
            scnfname += ".scn";
        loadScnFile(scnfname);
    }

    //debug
    export2obj();
    //exit(42);


    int lastFPS = -1;
    vector3df lastpos(.0,.0,.0), pos;
    while(device->run())
    if (device->isWindowActive())
	{
        driver->beginScene(true, true, SColor(255,100,101,140));
		smgr->drawAll();
		segui->getIrrGUIEnv()->drawAll();

        /*if (selected)
        {
            driver->draw3DTriangle(tri, video::SColor(0,255,0,0));
        }*/
        if (sel)
            for (u32 i=0; i<sel->boxes.size(); i++)
                driver->draw3DBox(sel->boxes[i],SColor(125,0,0,255));

		driver->endScene();

		int fps = driver->getFPS();
		if (lastFPS != fps)
		{
		  core::stringw str = L"SWAT3 SCN editor FPS: ";
		  str += fps;
		  device->setWindowCaption(str.c_str());
		  lastFPS = fps;

		}
        whereami();
	}

    device->drop();

    return 0;
}

void selectCurSurface(bool bAppend)
{

    if (!scn) return;

    CScnSolid * solid = scn->getSolid(0);


    if (!node || !scnMesh)
        return;

    core::line3d<f32> line;
    ISceneCollisionManager * scmg=device->getSceneManager()->getSceneCollisionManager();


	line = scmg->getRayFromScreenCoordinates ( device->getCursorControl()->getPosition());
	//line.start = camera->getPosition();
	//line.end = line.start +
      //  (camera->getTarget() - line.start).normalize() * 1000.0f;

    core::vector3df intersection;
    const ISceneNode * outNode;

    core::triangle3df tri;
    selected = scmg->getCollisionPoint(line, selector, intersection, tri,outNode);


    vector3df pa=tri.pointA;
    vector3df pb=tri.pointB;
    vector3df pc=tri.pointC;

    selsurf=-1;

    for (u32 i=0; i<scn->getSolid(0)->n_surfs; i++)
    {
        scnSurf_t * surfi = &solid->surfs[i];

        bool foundpa=0; bool foundpb=0; bool foundpc=0;
        for (int j=0; j<surfi->vertidxlen; j++)
        {
            point3f * verti=&solid->verts[solid->vertidxs[surfi->vertidxstart + j]];
            vector3df vectori(verti->x, verti->y, verti->z);
            if (!foundpa)
            {
                if (vectori.equals(pa))
                    foundpa=1;

            }
            if (!foundpb)
            {
                if (vectori.equals(pb))
                    foundpb=1;
            }
            if (!foundpc)
            {
                if (vectori.equals(pc))
                    foundpc=1;
            }
            if (foundpa && foundpb && foundpc) //if found all vertices
            {
                selsurf=i;
                break;
            }
        }
        if (selsurf>-1)
            break;
    }
    if (selsurf==-1)
    {
        SAY("Can't find surface!\n");
        return;
    }

    sel->selectSurf(selsurf,bAppend);
    putBoxesInSharedVerts();

    segui->surfs_update(sel);

}

bool closeCurrentScnFile()
{
    camera->setInputReceiverEnabled(false);

    if (scn)
    {
        delete scn;
        scn=0;
    }

    if (scnMesh)
    {
        scnMesh->drop();
        scnMesh=0;
    }

    if (node)
    {
        node->remove();
        node=0;
    }

    selected = false;
    if (output)
    {
        output->close();
        delete output;
        output=0;
    }

    //TODO: clear texture list (using unique texture names)

    //delete selector
    if (sel)
    {
        delete sel;
        sel = 0;
    }

    return true;


}

bool loadScnFile(path fname)
{
    closeCurrentScnFile();

    s32 pos=fname.findLastChar("\\/",2); //search for \ or /
    if (pos>-1)
    {
        device->getFileSystem()->changeWorkingDirectoryTo((fname.subString(0,pos-1)));
        //fname=fname.subString(pos+1,fname.size()-pos);
        deletePathFromFilename(fname);
    }

    path bakname;
    cutFilenameExtension(bakname,fname);
    bakname += ".bak";

    if (!CopyFile_(fname.c_str(),bakname.c_str()))
      error(true,"can't copy from %s to %s",fname.c_str(),bakname.c_str());

    ifstream scnFile(fname.c_str(),ios::in | ios::binary);

    if (!scnFile.is_open())
        error(true,"Can't open %s",fname.c_str());

    scn = new CScn(&scnFile);   //load file

    scnFile.close();

    output = new ofstream(fname.c_str(), ios::in | ios::binary | ios::ate);

    scnMesh = scnCreateMesh(device,scn->getSolid(0));

    //http://irrlicht.sourceforge.net/phpBB2/viewtopic.php?t=24116&highlight=meshbuffer+material
/*This is because each mesh scene node optionally maintains a copy of the materials
[see IMeshSceneNode::setReadOnlyMaterials()]. When you say node->setMaterialTexture()
that modifies the copy held by that mesh scene node, and those are the materials used to render [by default].

When you modify the mesh buffer material via buf->getMaterial(), you are changing the materials
in the source mesh. Since those materials aren't used to render default constructed mesh scene nodes,
it will only take effect for mesh scene nodes created after you make that call.

You should either use node->setMaterialTexture() or node->getMaterial() if you want to modify
the material for an individual mesh scene node that uses its own material copy, or you can use the
technique you are currently using if you setReadOnlyMaterials(true). */

    node = device->getSceneManager()->addMeshSceneNode(scnMesh);
        if (node)
        {
            node->setReadOnlyMaterials(true);   //this allows us to edit materials by accessing the meshbuffers
            node->setMaterialFlag(EMF_LIGHTING, false);
            node->setAutomaticCulling(EAC_FRUSTUM_BOX );
            node->setVisible(true);
        }


	if (node)
	{

		selector = device->getSceneManager()->createTriangleSelector(scnMesh,node);
		node->setTriangleSelector(selector);

		selector->drop();

	}
	camera->setInputReceiverEnabled(true);

	//create surface selector
	sel = new CSelector(scn, scnMesh, device);

}

bool scnRetexture(path fname)
{
    s32 pos1,pos2,pos;
    stringc shortname = fname;

    char * ptex;

    deletePathFromFilename(shortname);
    cutFilenameExtension(shortname,shortname);
    shortname=shortname.subString(0,min_(shortname.size(),31u));
    //by using min we make sure we don't have tex names bigger than 32 chars (including zero terminated)

    SAY("Setting new texture to %s.\n",shortname.c_str());

    array<u32>& rs = sel->getRedSurfs();
    if (rs.size()==-1)
    {
        WARN("No surface was selected!\n");
        return false;
    }

    ITexture * t = device->getVideoDriver()->getTexture(fname);
    for (u32 i=0; i<rs.size(); i++)
    {
        u32 si = rs[i];
        if (si>=0 && si<scn->getSolid(selsolid)->n_surfs)
        {
            ptex = scn->getSolid(selsolid)->surfs[si].texture;
            strcpy(ptex, shortname.c_str());

            //pad string with zeros
            for (int j=shortname.size();j<32; j++)
                ptex[j] = '\0';

            //show new texture in viewer
            scnMesh->getMeshBuffer(si)->getMaterial().setTexture(0,t);
            //SMeshBuffer * buffer = (SMeshBuffer*)scnMesh->getMeshBuffer(si);
            //buffer->Material.setTexture(0,t);
        }
        else
        {
            WARN("Invalid surface index: Solid(%d).surf(%d)\n",selsolid,si);
            return false;
        }
    }
    return true;
}

//new version uses getSurfUVidxs so we only iterate once for each vertex
bool scnRetexture_UV(f32 uu, f32 vv)
//retexture after changing texture position (width,height,U,V)
{
    //ATTENTION: we only want to update each vertex once!
    array<u32>& rs = sel->getRedSurfs(); //selected surfs
    array<u32>& bs = sel->getBlueSurfs(); //shared surfs

    uu *= uvgridmult;
    vv *= uvgridmult;

    //SAY("Shifting UV by %f %f.\n",uu,vv);

    if (rs.size()==0)
    {
        WARN("No surface was selected!\n");
        return false;
    }

    arrayu uvidxs = getSurfUVIdxs(rs);              //returns all uvidxs of red surfaces, non repeating
    u32 uvi;
    CScnSolid * solid = scn->getSolid(selsolid);

    while (uvidxs.iterate(uvi))
    {
        f32 U = solid->uvpos[uvi].u;
        f32 V = solid->uvpos[uvi].v;

        if (!uvresize) { //shift
            U += uu; V +=vv;
        } else {           //resize
            U *= (1.0-uu); V *= (1.0-vv);
        }
        solid->uvpos[uvi].u = U;
        solid->uvpos[uvi].v = V;
    }

    //update what is displayed on screen, all red surfs
    updateBufferUVfromScnSurf(rs);

    //update what is displayed on screen, all blue surfs too
    updateBufferUVfromScnSurf(bs);


    return true;
}

void updateBufferUVfromScnSurf(array<u32> s)
{
    for (u32 i=0; i<s.size(); i++)
        updateBufferUVfromScnSurf(s[i]);
}

void updateBufferUVfromScnSurf(u32 si)
{
    CScnSolid * solid = scn->getSolid(selsolid);
    scnSurf_t * surfi = &solid->surfs[si];
    SMeshBuffer * buffer = (SMeshBuffer*)scnMesh->getMeshBuffer(si);

    for(u32 i=0; i<surfi->vertidxlen; i++)
    {
        point2f uvi = solid->uvpos[solid->uvidxs[surfi->vertidxstart + i]];
        buffer->Vertices[i].TCoords.X = uvi.u;
        buffer->Vertices[i].TCoords.Y = uvi.v;
    }
}

bool scnSaveFile()
{
    CScnSolid * solid = scn->getSolid(selsolid);

    //Gravar texture names

    if (output->is_open())
    {

        //save textures to file
        for (int i=0; i<solid->n_surfs; i++)
        {
            output->seekp(solid->surfsad[i]);
            output->write(solid->surfs[i].texture,32);

        }

        //save uvpos
        output->seekp(solid->uvposad);
        u32 len = (solid->n_uvpos)*sizeof(point2f);
        output->write((char *) (solid->uvpos),len);

        SAY("Changes saved to file.\n");
        return true;
    }
    else
      error(false,"Output file stream is not open! Make sure it's not read only.");

}

void uvgrid_increase()
{
   uvgridmult *= 2.0;
   if (uvgridmult>1.0) uvgridmult = 1.0;
   SAY("Setting UV grid to %4.3f\n",uvgridmult);

   segui->uvgrid_setMult(uvgridmult);
}

void uvgrid_decrease()
{
   uvgridmult *= 0.5;
   SAY("Setting UV grid to %4.3f\n",uvgridmult);
   segui->uvgrid_setMult(uvgridmult);
}

void toggleUVresize()
{
   uvresize = !uvresize;

   if (uvresize) SAY("Now uv operation set to resize\n");
   else SAY("Now uv operation set to move\n");
   segui->uvgrid_setOp(uvresize);

}
char * getBaseDirectory()
{
    return BaseDirectory;
}

/*void split()
//splits the uv vertices of the red surfaces from the blue surfaces, so they can move independently
//i'm sure there's much redundancy in this function, as it should be better implemented in CSelector,
//but as long as it works...
{
    array<u32>& rs = sel->getRedSurfs();
    array<u32>& bs = sel->getBlueSurfs();
    array<vertProp_t> verts;

    for (u32 i=0; i<rs.size(); i++)
    {
        vprops = sel->getSurfVertProps(i);
        for (u32 ii=0; ii<verts.size(); ii++)
        {
            if (verts[ii].bShared) //if vertex is shared
            {
                for (u32 k=0; k<verts[ii].sharesWith.size(); k++)
                {
                    if (bs.binary_search(verts[ii].sharesWith[k])>-1) //if this vertex shares with a blue plane
                    {
                        makeNewUVVert(i,ii)
                }
            }
        }
    }

void makeNewUVVert(u32 si, u32 idxi)
{
}

}*/

arrayu getSurfUVIdxs(u32 si)
{
    arrayu uvidxs;
    CScnSolid * solid = scn->getSolid(selsolid);
    scnSurf_t * surfi = &solid->surfs[si];

    for (u32 i=0; i<surfi->vertidxlen; i++)
        uvidxs.push_back(solid->uvidxs[surfi->vertidxstart+i]);

    return uvidxs;
}

arrayu getSurfUVIdxs(arrayu ss)
{
    u32 si,uvi;
    arrayu all;

    while (ss.iterate(si))
    {
        arrayu uvis = getSurfUVIdxs(si);
        //cannot use while (getSurfUVIdxs(si).iterate(uvi)) or else it will keep calling getSurfUVIdxs(si) each time it iterates
        while(uvis.iterate(uvi))
            if (all.binary_search(uvi)==-1) //if not already there, add
                all.push_back(uvi);
    }
    return all;
}

arrayu getSurfUVIdxs(array<u32> ss)
{
    return getSurfUVIdxs(arrayu(ss));
}



list<u32> getSurfUVIdxs_list(u32 si)
{
    list<u32> uvidxs;
    CScnSolid * solid = scn->getSolid(selsolid);
    scnSurf_t * surfi = &solid->surfs[si];

    for (u32 i=0; i<surfi->vertidxlen; i++)
        uvidxs.push_back(solid->uvidxs[surfi->vertidxstart+i]);

    return uvidxs;
}

void putBoxesInSharedVerts()
{
    arrayu rs;
    arrayu bs;
    rs.push_back(sel->getRedSurfs());
    bs.push_back(sel->getBlueSurfs());

    u32 r,b,swi;
    vertProp_t vp;
    sel->boxes.set_used(0);

    while(rs.iterate(r))
    {
        myarray<vertProp_t> rvprops; rvprops.push_back(sel->getSurfVertProps(r));
        while(rvprops.iterate(vp))
        {
            arrayu sharesWith; sharesWith.push_back(vp.sharesWith);
            while(sharesWith.iterate(swi))
            {
                s32 bi = bs.binary_search(swi);
                if (bi > -1)
                {
                    //SAY("Surfs[%d]: vertex [%d]: shared with blue surf %d\n",r,vp.surf_vertidx,bs[bi]);

                    CScnSolid * solid = scn->getSolid(0);
                    point3f vert = solid->verts[solid->vertidxs[vp.vertidxidx]];
                    f32 dx,dy,dz=dx=dy=1.0;
                    sel->boxes.push_back(aabbox3df(vert.x-dx,vert.y-dy,vert.z-dz,vert.x+dx,vert.y+dy,vert.z+dz));
                }
            }
        }
    }

}

void saySurfProps()
//writes properties of last select surface
{
     /*u32 uvi;
    SAY("Red surfs uvidxs: ");
    arrayu verts = getSurfUVIdxs(sel->getRedSurfs());
    while(verts.iterate(uvi))
        SAY("%d ", uvi);
    SAY("\n");*/

    if (sel->getRedSurfs().size() == 0) return;

    u32 si = sel->getRedSurfs().getLast();
    scnSurf_t * surfi = &scn->getSolid(0)->surfs[si];

    SAY("+ surf[%d]:\n",si);
    SAY("|      texture = %s\n",surfi->texture);
    SAY("|    (unknown) = %.1f %.1f \n",surfi->unk[0],surfi->unk[1]);
    SAY("|        flags = %uh\n", surfi->flags);
    SAY("|        alpha = %uh\n", surfi->alpha);
    SAY("| lighmap size = %ux%u\n",surfi->lmsize_h, surfi->lmsize_v);
    SAY("| texture size = %ux%u\n",surfi->width, surfi->height);
    SAY("|verts idx idx = from %d to %d\n", surfi->vertidxstart, surfi->vertidxstart + surfi->vertidxlen);
    SAY("|  + plane idx = %u\n", surfi->planeidx);
    scnPlane_t * planei = &(scn->getSolid(0)->planes[surfi->planeidx]);
    SAY("|  | normal = (%.1f, %.1f, %.1f), d=%.1f\n",planei->a,planei->b,planei->c,planei->d);
    SAY("|      shading = %u\n",surfi->more);
    //print shadind info
    /*if (surfi->more)
    {
        u8 * ps = surfi->shading;
        for (u32 i=0; i<surfi->vertidxlen; i++)
        {
            SAY("               | %u %u %u %u\n",ps[0],ps[1],ps[2],ps[3]);
            ps+=4;
        }
    }*/
    SAY("|    (unknown 4 s16) = %hd %hd %hd %hd\n",surfi->stuff2[2],surfi->stuff2[4],surfi->stuff2[6],surfi->stuff2[8]);
    SAY("|    (unknown 2 s32) = %d %d \n",surfi->stuff2[2],surfi->stuff2[6]);
    //SAY("|        istga = %u\n",scnMesh->getMeshbuffer(si)->get

}
//using foreach, not working properly, (can't use getSurfUVIdxs inside foreach
/*void saySurfProps()
{
    SAY("Red surfs uvidxs: ");
    arrayu verts = getSurfUVIdxs(sel->getRedSurfs());

    foreach(u32, uvi, getSurfUVIdxs(sel->getRedSurfs()))
        SAY("%d ", *uvi);

//    for(arrayu::iterator uvi=verts.startIteration(); !uvi.hasEnded(); uvi++)
        //SAY("%d ", *uvi);

    SAY("\n");

}*/

vector3df lastpos(.0,.0,.0);
void whereami()
{
    if (!camera || !segui) return;

    vector3df pos= camera->getPosition();
    //should use instead of getAbsolutePosition, in case scene is not in origin

    if (pos!=lastpos)
    {
        //SAY("Where Am I? %.3f %.3f %.3f. Cell: %s\n",pos.X,pos.Y,pos.Z,scn->getSolid(0)->getCellAtPos(pos.X,pos.Y,pos.Z)->name);
        scnRawCell_t * cell = scn->getSolid(0)->getCellAtPos(pos.X,pos.Y,pos.Z);
        segui->pos_update(pos.X,pos.Y,pos.Z,cell->name);
        lastpos=pos;
    }
}

void export2obj()
{
    //scnExportObj(scn,"exported");
    //scnExport3ds(scn->getSolid(0),"exported");
    //scnExportMap(scn,"exported");
    //exit(42);
}

